package pl.ydp.components.mathcell.mathrecognizer.geometry
{
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import pl.ydp.components.mathcell.mathrecognizer.engine.Parameters;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveSubstanceType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.PrimitiveExtractor;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.PrimitiveExtractorTask;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.PrimitiveSubstance;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.quickshapes.QuickShape;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.quickshapes.QuickShapeTask;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.quickshapes.QuickShapeType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.optimizer.PrimitivesOptimizer;
	
	public class Primitive extends PrimitiveStructure
	{
		
		// -------------------- CTOR --------------------
		
		
		public function Primitive ( _vectors:Array, _substanceType:Number = 1, _helixity:Number = 0) {
			
			if (_vectors.length == 0){
				reset();
				return;
			}
			
			children = new Array;
			
			vectors = _vectors;
						
			process(_substanceType, _helixity);
			
		}
		
		
		// -------------------- PUBLIC FIELDS --------------------
		
		public var vectors:Array;
		
		public var direction:Number;
		public var directionBegin:Number;
		public var directionEnd:Number;
		
		public var type:Number;
		public var typeListed:Array;
		
		public var angle:Number;
		public var angleBegin:Number;
		public var angleEnd:Number;
		
		public var begin:Point;
		public var end:Point;
		public var length:Number;
		
		public var cogReal:Point;
		
		public var helix:Number;
		
		public var children:Array;
		
		// -------------------- PUBLIC METHODS --------------------
		
		public function reset():void{
			type = PrimitiveType.UNKNOWN;
			vectors = new Array();
			process();
		}
		
		//--------------------------------------------------------
		
		public function add(vectorToAdd:Array, shift:Boolean = false, reverse:Boolean = false):void{
			
			if ((!shift && reverse)  ||  (shift && !reverse)){
				vectorToAdd.reverse();
			}
			
			
			for each (var vec:EuclideanVector in vectorToAdd){
				if (reverse){
					vec.reverse();
				}
				if (shift){
					vectors.unshift(vec);
				} else {
					vectors.push(vec);
				}				
				
			}
			
			var testExtractor:PrimitiveExtractor = new PrimitiveExtractor(vectors, PrimitiveExtractorTask.LOOPS);
			
			var loopFound:Boolean = false;
			
			if (testExtractor.substances.length > 0){
				if (PrimitiveSubstanceType.isLoop(testExtractor.substances[0].type)){
					loopFound = true;
				}
			} else {
				
				var testQuickShape:QuickShape = new QuickShape(vectors, QuickShapeTask.LOOP);
				
				if (testQuickShape.type == QuickShapeType.LOOP){
					loopFound = true;
				}
				
			}
			
			if (PrimitiveType.isLoop(type)  ||  loopFound){
				
				process(PrimitiveSubstanceType.LOOP_POSITIVE, helix);
				
			} else if (PrimitiveType.isCurve(type)){
				
				process(PrimitiveSubstanceType.CURVE_POSITIVE, helix);
				
			} else if (PrimitiveType.isLine(type)){
				
				process(PrimitiveSubstanceType.LINE);
				
			}
		}
		
		//--------------------------------------------------------
		
		public function findChildren():void{
			
			if (!PrimitiveType.isCurve(type)){
				return
			}
			
			var childrenExtractor:PrimitiveExtractor = new PrimitiveExtractor(vectors, PrimitiveExtractorTask.LINES);
			var subst:PrimitiveSubstance;
			var prim:Primitive;
			
		
			for each(subst in childrenExtractor.substances){
				children.push(new Primitive(vectors.slice(subst.begin, subst.end+1), subst.type, subst.helixCount) );
			}
			
			children = PrimitivesOptimizer.optimize(children);
			
			if (children.length < 2){
				//children = new Array;
			}
			
				
		}
		
		// -------------------- PRIVATE METHODS --------------------
	
		private function process(_substanceType:Number = 1, _helixCount:Number = 0):void{
			
			if (vectors.length == 0  ||  _substanceType == PrimitiveSubstanceType.UNKNOWN){
				angle = 0;
				angleBegin = 0;
				angleEnd = 0;
				direction = VectorDirection.UNKNOWN;
				directionBegin = VectorDirection.UNKNOWN;
				directionEnd = VectorDirection.UNKNOWN;
				type = PrimitiveType.UNKNOWN;
				return;
			}
			
			var vec:EuclideanVector;
			
			// REVERSE CURVE
			
			if (_substanceType == PrimitiveSubstanceType.CURVE_NEGATIVE  ||  _substanceType == PrimitiveSubstanceType.LOOP_NEGATIVE){
				vectors.reverse();
				for each (vec in vectors){
					vec.reverse();
				}
			} 	
			
			// FIND ANGLE
			
			var tmpAngle:Number = vectors[0].angle;
			var v:Number;
			
			if (PrimitiveSubstanceType.isLine(_substanceType)){
				
				for (v = 1 ; v < vectors.length ; v ++){
					tmpAngle = Angle.meanAlpha(tmpAngle, vectors[v].angle,v/(v+1));
				}
				angle = Angle.points2degrees(vectors[0].point, vectors[vectors.length-1].point);
				angle = Angle.meanAlpha(tmpAngle, angle, 0.667);
				
			} else if (PrimitiveSubstanceType.isCurve(_substanceType)  ||  PrimitiveSubstanceType.isLoop(_substanceType) ){
				angle = Angle.points2degrees(vectors[0].point, vectors[vectors.length-1].point);
			} else {
				angle = 0;
			}
			
			
			// FIND DIRECTION
			
			direction = VectorDirection.formAngle(angle);
			
			// REVERSE
			
			if (PrimitiveSubstanceType.isLine(_substanceType)  &&  direction >= VectorDirection.REVERSED_BEGIN){
				vectors.reverse();
				for each (vec in vectors){
					vec.reverse();
				}
				angle = angle - 180;
				direction = VectorDirection.formAngle(angle);
			} 		
			
			// CONTINUE FIND ANGLE & DIRECTION
			
			//angle = Angle.angle2directionAngle(angle);
			
			var angleBeginFull:Number;
			var angleEndFull:Number;
			
			if (vectors.length > 8){
				
				if (Angle.difference(vectors[0].angle, vectors[2].angle) < Angle.difference(vectors[2].angle, vectors[4].angle)){
					angleBeginFull = Angle.mean(vectors[0].angle, vectors[2].angle);
				} else {
					angleBeginFull = Angle.mean(vectors[2].angle, vectors[4].angle);
				}
				
				if (Angle.difference(vectors[vectors.length-3].angle, vectors[vectors.length-1].angle) < Angle.difference(vectors[vectors.length-5].angle, vectors[vectors.length-3].angle)){
					angleEndFull = Angle.mean(vectors[vectors.length-3].angle, vectors[vectors.length-1].angle);
				} else {
					angleEndFull = Angle.mean(vectors[vectors.length-5].angle, vectors[vectors.length-3].angle);
				}
				
				angleBegin = angleBeginFull;
				angleEnd = angleEndFull;
				
			} else if (vectors.length > 4){
				
				if (Angle.difference(vectors[0].angle, vectors[1].angle) < Angle.difference(vectors[1].angle, vectors[2].angle)){
					angleBeginFull = Angle.mean(vectors[0].angle, vectors[1].angle);
				} else {
					angleBeginFull = Angle.mean(vectors[1].angle, vectors[2].angle);
				}
				
				if (Angle.difference(vectors[vectors.length-2].angle, vectors[vectors.length-1].angle) < Angle.difference(vectors[vectors.length-3].angle, vectors[vectors.length-2].angle)){
					angleEndFull = Angle.mean(vectors[vectors.length-2].angle, vectors[vectors.length-1].angle);
				} else {
					angleEndFull = Angle.mean(vectors[vectors.length-3].angle, vectors[vectors.length-2].angle);
				}
				
				angleBegin = angleBeginFull;
				angleEnd = angleEndFull;
				
			} else if (vectors.length > 2){
				
				angleBeginFull = Angle.mean(vectors[0].angle, vectors[1].angle);
				angleEndFull   = Angle.mean(vectors[vectors.length-2].angle, vectors[vectors.length-1].angle);

				angleBegin = angleBeginFull;
				angleEnd = angleEndFull;
			} else {
				angleBeginFull = angle;
				angleEndFull = angle;
				angleBegin = angle;
				angleEnd = angle;
			}	
			
			directionBegin = VectorDirection.formAngle(angleBeginFull);
			directionEnd   = VectorDirection.formAngle(angleEndFull);	
			
			// FIND HELIX 
			
			helix = _helixCount;
						
			// FIND TYPE
			if (PrimitiveSubstanceType.isFlourish(_substanceType)){
				
				type = PrimitiveType.FLOURISH;
				
			} else if (PrimitiveSubstanceType.isLoop(_substanceType)){
				
				type = PrimitiveType.LOOP;
				
			} else if (PrimitiveSubstanceType.isCurve(_substanceType)){
				
				if (helix > 1){
					type = PrimitiveType.HELIX;
				} else {
					type = PrimitiveType.primitiveCurveTypeFromDirections(directionBegin, directionEnd);
				}
				
				typeListed = new Array;
				
				for (var tx:Number = 0 ; tx < 8 ; tx ++ ){
					if ((type & 1024*Math.pow(2,tx)) == 1024*Math.pow(2,tx)){
						typeListed.push(true);
					} else {
						typeListed.push(false);
					}
				}
				
				
			} else if (PrimitiveSubstanceType.isLine(_substanceType)  ||  PrimitiveSubstanceType.isWave(_substanceType)){
				
				switch (direction){
					case (0):
					case (4):
						type = PrimitiveType.LINE0;
						break;
					case (1):
					case (5):
						type = PrimitiveType.LINE1;
						break;
					case (2):
					case (6):
						type = PrimitiveType.LINE2;
						break;
					case (3):
					case (7):
						type = PrimitiveType.LINE3;
						break;
				}
				
				if (PrimitiveSubstanceType.isWave(_substanceType)){
					type = type | PrimitiveType.WAVE;					
				}
				
				
			} else if (_substanceType == PrimitiveSubstanceType.DOT){ 
			
				type = PrimitiveType.DOT;
				
			} 
			
			// FIND BEGIN, END & LENGTH
			
			begin = vectors[0].point.clone();
			end = vectors[vectors.length-1].findEnd().clone();
			length = vectors.length;
			
		
			// FIND COG & FIELD
			
			findCOG();
			findField();
			
			
		}
		
		// -------------------- PRIVATE WORK METHODS --------------------
		
		private function findCOG():void{
			
			if (vectors.length == 0){
				cog = new Point(0,0);
				return;
			}
			
			cog = new Point();
			
			for each (var v:EuclideanVector in vectors){
				cog.x += v.point.x;
				cog.y += v.point.y;
			}
			
			cog.x /= vectors.length;
			cog.y /= vectors.length;
			
			cogReal = findRealCOG();
		}
		
		//--------------------------------------------------------
		
		private function findRealCOG(step:Number = -1):Point{
			
			if (vectors.length == 0){
				return new Point(0,0);
			}
			
			var v:Number;
			var result:Point;
			
			const OPERATIVE_STEP:Number = Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT/2;
			
			if (step == -1){
				step = Math.round(OPERATIVE_STEP);
			}
			
			result = vectors[0].point.clone();
			
			for (v = step ; v < vectors.length ; v += step){
				result.x += vectors[v].point.x;
				result.y += vectors[v].point.y; 
			}
			
			result.x /= Math.ceil(vectors.length/step);
			result.y /= Math.ceil(vectors.length/step);
			
			return result;
		}
		
		
		//--------------------------------------------------------
		
		private function findField():void{
			
			if (vectors.length == 0){
				field = new Rectangle(0,0,0,0);
				return;
			}
			
			var min:Point = new Point(Infinity, Infinity);  
			var max:Point = new Point(-Infinity, -Infinity);
			
			for each (var currVector:EuclideanVector in vectors){
				
				if (currVector.point.x > max.x){
					max.x = currVector.point.x;}
				if (currVector.point.y > max.y){
					max.y = currVector.point.y;}
				if (currVector.point.x < min.x){
					min.x = currVector.point.x;}
				if (currVector.point.y < min.y){
					min.y = currVector.point.y;}
						
			}  // end for each currPoint
			
			
			// check the END - the end of last vector (NOT the point of last vector)
			
			if (end.x > max.x){
				max.x = end.x;
			}
			if (end.y > max.y){
				max.y = end.y;
			}
			if (end.x < min.x){
				min.x = end.x;
			}
			if (end.y < min.y){
				min.y = end.y;
			}
				
			
			field =  new Rectangle(min.x, min.y, max.x - min.x, max.y - min.y);
			
			}
		
		//--------------------------------------------------------
		
		
		
	}
}